// Copyright (c) 2024 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.


#pragma once

#include <stdlib.h>

#include <cstdint>
#include <ioxx_include_device.hpp>

#if __has_include("FreeRTOS.h")

    #error "去用 RTOS 专门的版本"

// 如果存在这个头文件，默认就表示要使用FreeRTOS，需要做一些相关的处理

    #include <FreeRTOS.h>

    #define _SCHEDULER__BASIC__SYSTICK__COUNTER_USE__FREE__RTOS

    // 如果RTOS 频率设为1000，则SysTick 中断周期是1ms，设为100 就是10ms，以此类推
    #define _SCHEDULER_TICK_SYS_TICK_CYCLE_N_MS (static_cast<uint32_t>(1000 / configTICK_RATE_HZ))

#endif


extern "C" {
// 在Systick 中断里调用，更新全局计数器
extern void scheduler_basic_systick_counter_inc_counter_from_isr();

extern uint32_t SysTick_Config(uint32_t ticks);

extern uint32_t SystemCoreClock;

#ifdef _SCHEDULER__BASIC__SYSTICK__COUNTER_USE__FREE__RTOS

extern void vPortSetupTimerInterrupt(void);  // 使用freertos 时，用它提供的systick 配置函数

#endif
}


/* 如果用了FREE RTOS，就以它的时钟配置为准
 * Systick counter 可以同时兼容HAL 和RTOS，方法是用systick_counter_hal_override.cpp 中定义的函数覆盖HAL 库的weak 函数。
 *
 * 在HAL 库初始化阶段，SystemCoreClock 首先为8MHz，HAL 代码调用setup_systick 配置SysTick 的中断周期，
 * setup_systick 再调用RTOS 的配置函数，保证配置不会冲突。
 *
 * 系统时钟配置完成，PLL 启动后，SystemCoreClock 将被HAL 代码更新为72MHz，然后systick_counter 再次配置SysTick 中断周期。
 */

namespace scheduler_basic {

#ifdef _SCHEDULER_TICK_SYS_TICK_CYCLE_N_MS

    static_assert(_SCHEDULER_TICK_SYS_TICK_CYCLE_N_MS > 0);
    constexpr uint32_t SYS_TICK_CYCLE_N_MS = _SCHEDULER_TICK_SYS_TICK_CYCLE_N_MS;

#elif defined(_SCHEDULER_TICK_SYS_TICK_CYCLE_1_MS)

    constexpr uint32_t SYS_TICK_CYCLE_N_MS = 1;  // 中断周期1 毫秒

#elif defined(_SCHEDULER_TICK_SYS_TICK_CYCLE_100_MS)

    constexpr uint32_t SYS_TICK_CYCLE_N_MS = 100;  // 中断周期100 毫秒

#else

    constexpr uint32_t SYS_TICK_CYCLE_N_MS = 10;  // 默认中断周期10 毫秒

#endif

    // 每毫秒SysTick 计数值，调用setup_systick 更新
    extern volatile uint32_t global_variable_ticks_per_milli_second;

    // 每微秒SysTick 计数值，调用setup_systick 更新
    extern volatile uint32_t global_variable_ticks_per_micro_second;


    // 根据新的系统时钟参数更新全局变量
    inline void update_systick_counter() {
        global_variable_ticks_per_micro_second = SystemCoreClock / 1000'000;  // 单引号作为数字分隔符，C++17
        global_variable_ticks_per_milli_second = global_variable_ticks_per_micro_second * 1000;
    }


    // 根据指定的中断周期配置SysTick，使能SysTick 中断，用CPU 时钟驱动计数
    inline int setup_systick() {
        update_systick_counter();

        uint32_t ticks = global_variable_ticks_per_milli_second * SYS_TICK_CYCLE_N_MS;

#ifdef _SCHEDULER__BASIC__SYSTICK__COUNTER_USE__FREE__RTOS

        // 调用FreeRTOS 的配置函数
        vPortSetupTimerInterrupt();
        return 0;
#else

        return SysTick_Config(ticks);
#endif
    }


    extern volatile uint32_t global_variable_ms_counter;

    extern volatile uint32_t global_variable_us_counter;


    /**
     * @brief 获取毫秒时间戳，不能在中断函数中使用
     *
     * SYS_TICK_CYCLE_N_MS > 1 时，需要用SysTick 当前值VAL 做计算，补全不足SYS_TICK_CYCLE_N_MS 的零头部分。
     * 由于SysTick 计数器持续运行，计算中可能出现的问题有：
     *
     * 1. 如果SysTick 中断被其他中断卡停了，计数变量ms_counter 没有及时更新，但SysTick->VAL 寄存器自动重装了，
     *    那么clock_ms 和clock_us 的前一次返回值可能比后一次小，就像ms_counter 发生了溢出一样，计算得到的两次时间间隔
     *    将远远大于实际值，导致延时过早结束；
     *
     *    解决方法：不在临界区和中断函数中使用clock_ms 或clock_us，只能用不依赖全局变量的clock_ticks。其他普通代码运行时，SysTick
     *    中断不会被阻滞，就不会有这种bug。
     *
     * 2. SysTick->VAL 已经重装，但是ms_counter 由于中断延迟等原因没有及时更新。
     *
     */
    inline uint32_t clock_ms() {
        if constexpr (SYS_TICK_CYCLE_N_MS > 1) {
            // RTOS 多任务抢占环境下，静态变量也要用volatile 标记
            // 并且写入变量时要进入临界区，避免函数重入的问题
            static volatile uint32_t last_ms = 0;
            uint32_t ms = global_variable_ms_counter;
            uint32_t v = SysTick->VAL;


            uint32_t ticks_per_cycle = SysTick->LOAD + 1;
            ms += (ticks_per_cycle - v) / global_variable_ticks_per_milli_second;

            do {
                // 要改动静态变量，进入临界区
                // 不进了，另外提供个RTOS 下可重入的实现

                uint32_t last_tmp_read_only = last_ms;

                // 这部分代码用来避免SysTick-VAL 重置导致计算结果异常
                if (last_tmp_read_only > ms && (last_tmp_read_only - ms) < (SYS_TICK_CYCLE_N_MS)) {
                    ms = last_tmp_read_only;
                }
                else {
                    last_ms = ms;
                }
            } while (0);

            return ms;
        }
        else {
            return global_variable_ms_counter;
        }
    }


    /**
     * @brief 获取微秒时间戳
     *
     */
    inline uint32_t clock_us() {
        static volatile uint32_t last_us = 0;
        uint32_t us = global_variable_us_counter;
        uint32_t v = SysTick->VAL;

        uint32_t ticks_per_cycle = SysTick->LOAD + 1;
        us += (ticks_per_cycle - v) / global_variable_ticks_per_micro_second;

        do {
            // irqxx::CriticalScope cs;

            auto last_tmp_read_only = last_us;

            // 这部分代码用来避免SysTick-VAL 重置导致计算结果异常
            if (last_tmp_read_only > us && (last_tmp_read_only - us) < (SYS_TICK_CYCLE_N_MS * 1000)) {
                us = last_tmp_read_only;
            }
            else {
                last_us = us;
            }
        } while (0);

        return us;
    }


    /**
     * @brief 获取CPU 时钟周期数， 可在中断函数中使用
     *
     * 最大计时周期等于SysTick 中断周期
     *
     * @return uint32_t
     */
    inline uint32_t clock_ticks() {
        // 把Systick 计数变成从小到大增加的顺序，方便使用
        return SysTick->LOAD - SysTick->VAL;
    }


    /**
     * @brief 与clock_ticks 函数配合，可以在不能用clock_ms 和clock_us 的地方获取时间差
     *
     * @param ticks
     * @return uint32_t
     */
    inline uint32_t ticks_to_ms(uint32_t ticks) {
        return ticks / global_variable_ticks_per_milli_second;
    }


    /**
     * @brief 与clock_ticks 函数配合，可以在不能用clock_ms 和clock_us 的地方获取时间差
     *
     * @param ticks
     * @return uint32_t
     */
    inline uint32_t ticks_to_us(uint32_t ticks) {
        return ticks / global_variable_ticks_per_micro_second;
    }


    inline uint32_t us_to_ticks(uint32_t us) {
        return us * global_variable_ticks_per_micro_second;
    }


}  // namespace scheduler_basic


// systick 头文件公用的头文件片段，必须放在这里include
#include "./systick_counter/systick_counter_common_fragment.hpp"